import {Fragment, useEffect, useState} from 'react';
import styled from '@emotion/styled';

import {bulkDelete, bulkUpdate, mergeGroups} from 'sentry/actionCreators/group';
import {
  addErrorMessage,
  addLoadingMessage,
  clearIndicators,
} from 'sentry/actionCreators/indicator';
import {Alert} from 'sentry/components/alert';
import Checkbox from 'sentry/components/checkbox';
import {Sticky} from 'sentry/components/sticky';
import {t, tct, tn} from 'sentry/locale';
import GroupStore from 'sentry/stores/groupStore';
import ProjectsStore from 'sentry/stores/projectsStore';
import SelectedGroupStore from 'sentry/stores/selectedGroupStore';
import {useLegacyStore} from 'sentry/stores/useLegacyStore';
import {space} from 'sentry/styles/space';
import type {Group, PageFilters} from 'sentry/types';
import {trackAnalytics} from 'sentry/utils/analytics';
import {uniq} from 'sentry/utils/array/uniq';
import theme from 'sentry/utils/theme';
import useApi from 'sentry/utils/useApi';
import useMedia from 'sentry/utils/useMedia';
import useOrganization from 'sentry/utils/useOrganization';
import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState';
import type {IssueUpdateData} from 'sentry/views/issueList/types';
import {SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY} from 'sentry/views/issueList/utils';

import ActionSet from './actionSet';
import Headers from './headers';
import IssueListSortOptions from './sortOptions';
import {BULK_LIMIT, BULK_LIMIT_STR, ConfirmAction} from './utils';

type IssueListActionsProps = {
  allResultsVisible: boolean;
  displayReprocessingActions: boolean;
  groupIds: string[];
  onDelete: () => void;
  onSelectStatsPeriod: (period: string) => void;
  onSortChange: (sort: string) => void;
  query: string;
  queryCount: number;
  selection: PageFilters;
  sort: string;
  statsPeriod: string;
  onActionTaken?: (itemIds: string[], data: IssueUpdateData) => void;
};

function ActionsBarPriority({
  anySelected,
  narrowViewport,
  displayReprocessingActions,
  pageSelected,
  queryCount,
  selectedIdsSet,
  multiSelected,
  allInQuerySelected,
  query,
  handleDelete,
  handleMerge,
  handleUpdate,
  sort,
  selectedProjectSlug,
  onSortChange,
  onSelectStatsPeriod,
  isSavedSearchesOpen,
  statsPeriod,
  selection,
}: {
  allInQuerySelected: boolean;
  anySelected: boolean;
  displayReprocessingActions: boolean;
  handleDelete: () => void;
  handleMerge: () => void;
  handleUpdate: (data: IssueUpdateData) => void;
  isSavedSearchesOpen: boolean;
  multiSelected: boolean;
  narrowViewport: boolean;
  onSelectStatsPeriod: (period: string) => void;
  onSortChange: (sort: string) => void;
  pageSelected: boolean;
  query: string;
  queryCount: number;
  selectedIdsSet: Set<string>;
  selectedProjectSlug: string | undefined;
  selection: PageFilters;
  sort: string;
  statsPeriod: string;
}) {
  const shouldDisplayActions = anySelected && !narrowViewport;

  const sortDropdown = (
    <IssueListSortOptions sort={sort} query={query} onSelect={onSortChange} />
  );

  return (
    <ActionsBarContainer>
      {!narrowViewport && (
        <ActionsCheckbox isReprocessingQuery={displayReprocessingActions}>
          <Checkbox
            onChange={() => SelectedGroupStore.toggleSelectAll()}
            checked={pageSelected || (anySelected ? 'indeterminate' : false)}
            disabled={displayReprocessingActions}
          />
        </ActionsCheckbox>
      )}
      {!displayReprocessingActions && (
        <HeaderButtonsWrapper>
          {shouldDisplayActions && (
            <ActionSet
              queryCount={queryCount}
              query={query}
              issues={selectedIdsSet}
              allInQuerySelected={allInQuerySelected}
              anySelected={anySelected}
              multiSelected={multiSelected}
              selectedProjectSlug={selectedProjectSlug}
              onShouldConfirm={action =>
                shouldConfirm(action, {pageSelected, selectedIdsSet})
              }
              onDelete={handleDelete}
              onMerge={handleMerge}
              onUpdate={handleUpdate}
            />
          )}
          {!anySelected ? sortDropdown : null}
        </HeaderButtonsWrapper>
      )}
      {!anySelected ? (
        <Headers
          onSelectStatsPeriod={onSelectStatsPeriod}
          selection={selection}
          statsPeriod={statsPeriod}
          isReprocessingQuery={displayReprocessingActions}
          isSavedSearchesOpen={isSavedSearchesOpen}
        />
      ) : (
        <SortDropdownMargin>{sortDropdown}</SortDropdownMargin>
      )}
    </ActionsBarContainer>
  );
}

function IssueListActions({
  allResultsVisible,
  displayReprocessingActions,
  groupIds,
  onActionTaken,
  onDelete,
  onSelectStatsPeriod,
  onSortChange,
  queryCount,
  query,
  selection,
  sort,
  statsPeriod,
}: IssueListActionsProps) {
  const api = useApi();
  const organization = useOrganization();
  const {
    pageSelected,
    multiSelected,
    anySelected,
    allInQuerySelected,
    selectedIdsSet,
    selectedProjectSlug,
    setAllInQuerySelected,
  } = useSelectedGroupsState();
  const [isSavedSearchesOpen] = useSyncedLocalStorageState(
    SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY,
    false
  );

  const hasIssuePriority = organization.features.includes('issue-priority-ui');

  const disableActions = useMedia(
    `(max-width: ${
      isSavedSearchesOpen ? theme.breakpoints.xlarge : theme.breakpoints.medium
    })`
  );

  const numIssues = selectedIdsSet.size;

  function actionSelectedGroups(callback: (itemIds: string[] | undefined) => void) {
    const selectedIds = allInQuerySelected
      ? undefined // undefined means "all"
      : groupIds.filter(itemId => selectedIdsSet.has(itemId));

    callback(selectedIds);

    SelectedGroupStore.deselectAll();
  }

  // TODO: Remove issue.category:error filter when merging/deleting performance issues is supported
  // This silently avoids performance issues for bulk actions
  const queryExcludingPerformanceIssues = `${query ?? ''} issue.category:error`;

  function handleDelete() {
    actionSelectedGroups(itemIds => {
      bulkDelete(
        api,
        {
          orgId: organization.slug,
          itemIds,
          query: queryExcludingPerformanceIssues,
          project: selection.projects,
          environment: selection.environments,
          ...selection.datetime,
        },
        {
          complete: () => {
            onDelete();
          },
        }
      );
    });
  }

  function handleMerge() {
    actionSelectedGroups(itemIds => {
      mergeGroups(
        api,
        {
          orgId: organization.slug,
          itemIds,
          query: queryExcludingPerformanceIssues,
          project: selection.projects,
          environment: selection.environments,
          ...selection.datetime,
        },
        {}
      );
      if (selection.projects[0]) {
        const trackProject = ProjectsStore.getById(`${selection.projects[0]}`);
        trackAnalytics('issues_stream.merged', {
          organization,
          project_id: trackProject?.id,
          platform: trackProject?.platform,
          items_merged: allInQuerySelected ? 'all_in_query' : itemIds?.length,
        });
      }
    });
  }

  function handleUpdate(data: IssueUpdateData) {
    if ('status' in data && data.status === 'ignored') {
      const statusDetails =
        'ignoreCount' in data.statusDetails
          ? 'ignoreCount'
          : 'ignoreDuration' in data.statusDetails
            ? 'ignoreDuration'
            : 'ignoreUserCount' in data.statusDetails
              ? 'ignoreUserCount'
              : undefined;
      trackAnalytics('issues_stream.archived', {
        action_status_details: statusDetails,
        action_substatus: data.substatus,
        organization,
      });
    }

    if ('priority' in data) {
      trackAnalytics('issues_stream.updated_priority', {
        organization,
        priority: data.priority,
      });
    }

    actionSelectedGroups(itemIds => {
      // If `itemIds` is undefined then it means we expect to bulk update all items
      // that match the query.
      //
      // We need to always respect the projects selected in the global selection header:
      // * users with no global views requires a project to be specified
      // * users with global views need to be explicit about what projects the query will run against
      const projectConstraints = {project: selection.projects};

      if (itemIds?.length) {
        addLoadingMessage(t('Saving changes\u2026'));
      }

      bulkUpdate(
        api,
        {
          orgId: organization.slug,
          itemIds,
          data,
          query,
          environment: selection.environments,
          failSilently: true,
          ...projectConstraints,
          ...selection.datetime,
        },
        {
          success: () => {
            clearIndicators();
            onActionTaken?.(itemIds ?? [], data);
          },
          error: () => {
            clearIndicators();
            addErrorMessage(t('Unable to update issues'));
          },
        }
      );
    });
  }

  return (
    <StickyActions>
      {hasIssuePriority ? (
        <ActionsBarPriority
          query={query}
          queryCount={queryCount}
          selection={selection}
          statsPeriod={statsPeriod}
          onSortChange={onSortChange}
          allInQuerySelected={allInQuerySelected}
          pageSelected={pageSelected}
          selectedIdsSet={selectedIdsSet}
          displayReprocessingActions={displayReprocessingActions}
          handleDelete={handleDelete}
          handleMerge={handleMerge}
          handleUpdate={handleUpdate}
          multiSelected={multiSelected}
          narrowViewport={disableActions}
          selectedProjectSlug={selectedProjectSlug}
          isSavedSearchesOpen={isSavedSearchesOpen}
          sort={sort}
          anySelected={anySelected}
          onSelectStatsPeriod={onSelectStatsPeriod}
        />
      ) : (
        <ActionsBarContainer>
          {!disableActions && (
            <ActionsCheckbox isReprocessingQuery={displayReprocessingActions}>
              <Checkbox
                onChange={() => SelectedGroupStore.toggleSelectAll()}
                checked={pageSelected || (anySelected ? 'indeterminate' : false)}
                disabled={displayReprocessingActions}
              />
            </ActionsCheckbox>
          )}
          {!displayReprocessingActions && (
            <HeaderButtonsWrapper>
              {!disableActions && (
                <ActionSet
                  queryCount={queryCount}
                  query={query}
                  issues={selectedIdsSet}
                  allInQuerySelected={allInQuerySelected}
                  anySelected={anySelected}
                  multiSelected={multiSelected}
                  selectedProjectSlug={selectedProjectSlug}
                  onShouldConfirm={action =>
                    shouldConfirm(action, {pageSelected, selectedIdsSet})
                  }
                  onDelete={handleDelete}
                  onMerge={handleMerge}
                  onUpdate={handleUpdate}
                />
              )}
              <IssueListSortOptions sort={sort} query={query} onSelect={onSortChange} />
            </HeaderButtonsWrapper>
          )}
          <Headers
            onSelectStatsPeriod={onSelectStatsPeriod}
            selection={selection}
            statsPeriod={statsPeriod}
            isReprocessingQuery={displayReprocessingActions}
            isSavedSearchesOpen={isSavedSearchesOpen}
          />
        </ActionsBarContainer>
      )}
      {!allResultsVisible && pageSelected && (
        <Alert type="warning" system>
          <SelectAllNotice data-test-id="issue-list-select-all-notice">
            {allInQuerySelected ? (
              queryCount >= BULK_LIMIT ? (
                tct(
                  'Selected up to the first [count] issues that match this search query.',
                  {
                    count: BULK_LIMIT_STR,
                  }
                )
              ) : (
                tct('Selected all [count] issues that match this search query.', {
                  count: queryCount,
                })
              )
            ) : (
              <Fragment>
                {tn(
                  '%s issue on this page selected.',
                  '%s issues on this page selected.',
                  numIssues
                )}
                <SelectAllLink
                  onClick={() => setAllInQuerySelected(true)}
                  data-test-id="issue-list-select-all-notice-link"
                >
                  {queryCount >= BULK_LIMIT
                    ? tct(
                        'Select the first [count] issues that match this search query.',
                        {
                          count: BULK_LIMIT_STR,
                        }
                      )
                    : tct('Select all [count] issues that match this search query.', {
                        count: queryCount,
                      })}
                </SelectAllLink>
              </Fragment>
            )}
          </SelectAllNotice>
        </Alert>
      )}
    </StickyActions>
  );
}

function useSelectedGroupsState() {
  const [allInQuerySelected, setAllInQuerySelected] = useState(false);
  const selectedIds = useLegacyStore(SelectedGroupStore);

  const selected = SelectedGroupStore.getSelectedIds();
  const projects = [...selected]
    .map(id => GroupStore.get(id))
    .filter((group): group is Group => !!group?.project)
    .map(group => group.project.slug);

  const uniqProjects = uniq(projects);
  // we only want selectedProjectSlug set if there is 1 project
  // more or fewer should result in a null so that the action toolbar
  // can behave correctly.
  const selectedProjectSlug = uniqProjects.length === 1 ? uniqProjects[0] : undefined;

  const pageSelected = SelectedGroupStore.allSelected();
  const multiSelected = SelectedGroupStore.multiSelected();
  const anySelected = SelectedGroupStore.anySelected();
  const selectedIdsSet = SelectedGroupStore.getSelectedIds();

  useEffect(() => {
    setAllInQuerySelected(false);
  }, [selectedIds]);

  return {
    pageSelected,
    multiSelected,
    anySelected,
    allInQuerySelected,
    selectedIdsSet,
    selectedProjectSlug,
    setAllInQuerySelected,
  };
}

function shouldConfirm(
  action: ConfirmAction,
  {pageSelected, selectedIdsSet}: {pageSelected: boolean; selectedIdsSet: Set<string>}
) {
  switch (action) {
    case ConfirmAction.RESOLVE:
    case ConfirmAction.UNRESOLVE:
    case ConfirmAction.ARCHIVE:
    case ConfirmAction.SET_PRIORITY:
    case ConfirmAction.UNBOOKMARK: {
      return pageSelected && selectedIdsSet.size > 1;
    }
    case ConfirmAction.BOOKMARK:
      return selectedIdsSet.size > 1;
    case ConfirmAction.MERGE:
    case ConfirmAction.DELETE:
    default:
      return true; // By default, should confirm ...
  }
}

const StickyActions = styled(Sticky)`
  z-index: ${p => p.theme.zIndex.issuesList.stickyHeader};

  /* Remove border radius from the action bar when stuck. Without this there is
   * a small gap where color can peek through. */
  &[data-stuck] > div {
    border-radius: 0;
  }
`;

const ActionsBarContainer = styled('div')`
  display: flex;
  min-height: 45px;
  padding-top: ${space(1)};
  padding-bottom: ${space(1)};
  align-items: center;
  background: ${p => p.theme.backgroundSecondary};
  border: 1px solid ${p => p.theme.border};
  border-top: none;
  border-radius: ${p => p.theme.panelBorderRadius} ${p => p.theme.panelBorderRadius} 0 0;
  margin: 0 -1px -1px;
`;

const ActionsCheckbox = styled('div')<{isReprocessingQuery: boolean}>`
  display: flex;
  align-items: center;
  padding-left: ${space(2)};
  margin-bottom: 1px;
  ${p => p.isReprocessingQuery && 'flex: 1'};
`;

const HeaderButtonsWrapper = styled('div')`
  @media (min-width: ${p => p.theme.breakpoints.large}) {
    width: 50%;
  }
  flex: 1;
  margin: 0 ${space(1)};
  display: grid;
  gap: ${space(0.5)};
  grid-auto-flow: column;
  justify-content: flex-start;
  white-space: nowrap;
`;

const SelectAllNotice = styled('div')`
  display: flex;
  flex-wrap: wrap;
  justify-content: center;

  a:not([role='button']) {
    color: ${p => p.theme.linkColor};
    border-bottom: none;
  }
`;

const SelectAllLink = styled('a')`
  margin-left: ${space(1)};
`;

const SortDropdownMargin = styled('div')`
  margin-right: ${space(1)};
`;

export {IssueListActions};

export default IssueListActions;
